<?php
class mutiApnsInit {
	/**
	 * Array of APNS Connection Settings
	 *
	 * @var array
	 * @access private
	 */
	private $apnsData;

	/**
	 * Streams connected to APNS server[s]
	 *
	 * @var array
	 * @access private
	 */
	private $sslStreams;

	/**
	 * Absolute path to your Production Certificate
	 *
	 * @var string
	 * @access private
	 */
	private $certificate;
	private $passphrase;

	/**
	 * Apples Production APNS Gateway
	 *
	 * @var string
	 * @access private
	 */
	private $ssl = 'ssl://gateway.push.apple.com:2195';

	/**
	 * Apples Production APNS Feedback Service
	 *
	 * @var string
	 * @access private
	 */
	private $feedback = 'ssl://feedback.push.apple.com:2196';

	/**
	 * Absolute path to your Development Certificate
	 *
	 * @var string
	 * @access private
	 */
	private $sandboxCertificate;

	/**
	 * Apples Sandbox APNS Gateway
	 *
	 * @var string
	 * @access private
	 */
	private $sandboxSsl = 'ssl://gateway.sandbox.push.apple.com:2195';

	/**
	 * Apples Sandbox APNS Feedback Service
	 *
	 * @var string
	 * @access private
	 */
	private $sandboxFeedback = 'ssl://feedback.sandbox.push.apple.com:2196';

        private $retry=0; //记录重试的次数
        const retryLimit=5; //重试次数限制
        private static $statusDesc=array(
                0 => 'No errors encountered',
                1 => 'Processing error',
                2 => 'Missing device token',
                3 => 'Missing topic',
                4 => 'Missing payload',
                5 => 'Invalid token size',
                6 => 'Invalid topic size',
                7 => 'Invalid payload size',
                8 => 'Invalid token',
                255 => 'None (unknown)',
        );
        
	public function loadcertificate($config){
		$this->certificate = $config['certificate'];//CERTIFICATE;
		$this->sandboxCertificate = $config['sandboxcertificate'];//SANDBOXCERTIFICATE;
		$this->passphrase = $config['apns_pass_phrase'];//APNS_PASS_PHRASE;
		$this->sanbox_passphrase = $config['sandbox_apns_pass_phrase'];//APNS_PASS_PHRASE;
		$this->apnsData = array(
			'production' => array(
				'certificate' => $this->certificate,
				'ssl' => $this->ssl,
				'feedback' => $this->feedback,
				'passphrase' => $this->passphrase
			),
			'sandbox' => array(
				'certificate' => $this->sandboxCertificate,
				'ssl' => $this->sandboxSsl,
				'feedback' => $this->sandboxFeedback,
				'passphrase' => $this->sanbox_passphrase
			)
		);
	}
        /**
         * 推送
         * @param string $message 消息（不超过256px）
         * @param string $message
         */
	public function push($message, $tokenArr, $development = null) {
                if( empty($tokenArr) || !is_array($tokenArr) ){  return false; }
                var_dump($tokenArr);echo '<br>';
		if (!$development) {
			$development = 'production';
		}
		$json_encoded_message = urldecode(json_encode($message));
		$json_encoded_message = str_replace(array("\r\n", "\r", "\n"), "", $json_encoded_message);
                $length=strlen($json_encoded_message);
		$fp = false;
		if (isset($this->sslStreams[$development])) {
			$fp = $this->sslStreams[$development];
		} else {
			$fp = $this->_connectSSLSocket($development);
		}
		if (!$fp) {
			writeLog(date("Y-m-d H:i:s")."||A connected socket to APNS wasn't available\r\n", 'apns_error');
                        if( $this->retry>=self::retryLimit ){
                             writeLog(date("Y-m-d H:i:s")."||A connected socket to APNS had retried max times .tokens(".implode("|",$tokenArr).").\r\n", 'apns_error');
                             $this->retry=0;
                             return false;
                        }
                        ++$this->retry;
                        return self::push($message, $tokenArr, $development);
		}
		$expiry = time() + 604800; // 1周超时
                $tokenArr=  array_values($tokenArr);$reTokenArr=array();
                foreach($tokenArr as $key=>$token){
                         $pid=$key+1;
                         $msg = chr(1) . pack("N", $pid) . pack("N", $expiry) . pack("n", 32) . pack('H*', $token) . pack("n",$length ) . $json_encoded_message;
                         $fwrite = @fwrite($fp, $msg);
                         echo $token."<br>";
                         if (!$fwrite) {
                              echo "unwrite:".$token."<br>";
                             //$res=$this->rewrite($fp, $msg, $development);
                               writeLog ( date ( "Y-m-d H:i:s" ) . "||Failed writing to stream.\r\n", 'apns_error' );
                               if( $this->retry>=self::retryLimit ){
                                       writeLog(date("Y-m-d H:i:s")."||Failed writing to stream.APNS had retried max times .tokens(".implode("|",$tokenArr).").\r\n", 'apns_error');
                                       $this->retry=0;
                                       return false;
                               }                                     
                               ++$this->retry;
                               $reTokenArr=array_slice($tokenArr,$key);break;
		         }
                }
                $tv_sec = 2;
		$tv_usec = null; // Timeout. 1 million micro seconds = 1 second
		$r = array($fp);
		$we = null; // Temporaries. "Only variables can be passed as reference."
		$numChanged = stream_select($r, $we, $we, $tv_sec, $tv_usec);
                var_dump($numChanged);
		if (false === $numChanged) {
			writeLog(date("Y-m-d H:i:s")."||Failed selecting stream to read.tokens(".implode("|",$tokenArr).").\r\n",'apns_error');
                        if( !empty($reTokenArr) ){
                            return self::push($message, $reTokenArr, $development);
                        }
                        $this->retry=0;
                        return false;
		} else if ($numChanged > 0) {
                        //经过多次测试，发现存在$numChanged==1，读出的数据流都是空，测试也没有收到推送消息。
			$command = ord(fread($fp, 1));
			$status = ord(fread($fp, 1));
			$identifier_string = fread($fp, 4);
                        echo "COMMAND:".$command."STATUS:".$status;echo "<br>";
                        var_dump($identifier_string);
			if($command==8){
                              if ($status > 0) {
                                        $identifier = implode('', unpack("N", $identifier_string));
					$desc = isset(self::$statusDesc[$status]) ? self::$statusDesc[$status] : 'Unknown';
					writeLog(date("Y-m-d H:i:s")."||APNS responded with error for pid($identifier). status($status: $desc).token({$tokenArr[$identifier-1]}).\r\n", 'apns_error');
					$this->_closeSSLSocket($development);
                                        if( !($identifier>0) ){ $identifier=1;}
                                        $reTokenArr=array_slice($tokenArr,$identifier);
                                        sleep(1);
				}
			}else{
                             if( $this->retry>=self::retryLimit ){
                                     writeLog(date("Y-m-d H:i:s")."||APNS responded with blank stream.APNS had retried max times .tokens(".implode("|",$tokenArr).").\r\n", 'apns_error');
                                     $this->retry=0;
                                     return false;
                              }                                     
                              ++$this->retry;                            
                              writeLog(date("Y-m-d H:i:s")."||APNS responded with blank stream.\r\n", 'apns_error');
			      $this->_closeSSLSocket($development);
                              sleep(1);  
                              $reTokenArr=$tokenArr;
                        }
		}		
                if( !empty($reTokenArr) ){
                      return self::push($message, $reTokenArr, $development);
                }
                $this->retry=0;
		return true;
	}
	/**
	 * Connect the SSL stream (sandbox or production)
	 *
	 * @param $development string Development environment - sandbox or production
	 * @return bool|resource status whether the socket connected or not.
	 * @access private
	 */
	private function _connectSSLSocket($development) {
		$ctx = stream_context_create();
		stream_context_set_option($ctx, 'ssl', 'local_cert', $this->apnsData[$development]['certificate']);
		if ($this->apnsData[$development]['passphrase']) {
			stream_context_set_option($ctx, 'ssl', 'passphrase', $this->apnsData[$development]['passphrase']);
		}
		$this->sslStreams[$development] = @stream_socket_client($this->apnsData[$development]['ssl'], $error, $errorString, 100, (STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT), $ctx);
		if (!$this->sslStreams[$development]) {
			writeLog("Failed to connect to APNS: {$error} {$errorString}.\r\n", 'apns_error');
			unset($this->sslStreams[$development]);
			return false;
		}
		return $this->sslStreams[$development];
	}
	/**
	 * Close the SSL stream (sandbox or production)
	 *
	 * @param $development string Development environment - sandbox or production
	 * @return void
	 * @access private
	 */
	public function _closeSSLSocket($development) {
		if (isset($this->sslStreams[$development])) {
			fclose($this->sslStreams[$development]);
			unset($this->sslStreams[$development]);
		}
	}
       /**
        * 重新写入--防异常补充机制
        */
        private function rewrite(&$fp,$msg,$development){
          		writeLog(date("Y-m-d H:i:s")."||Failed writing to stream.\r\n", 'apns_error');
			$this->_closeSSLSocket($development);
			//try again
			$fp = $this->_connectSSLSocket($development);
			if (!$fp) {
				writeLog(date("Y-m-d H:i:s")."||A connected socket to APNS wasn't available ||in retry\r\n", 'apns_error');
				return false;
			}
			$fwrite = fwrite($fp, $msg);
			if (!$fwrite) {
				writeLog(date("Y-m-d H:i:s")."||Failed writing to stream.||in retry\r\n", 'apns_error');
				$fp = $this->_connectSSLSocket($development);
				return false;
			}
                        return true;
        }
	/**
	 * Fetch APNS Messages
	 *
	 * This will check with APNS for any invalid tokens and disable them from receiving further notifications.
	 *
	 * @param string $development Which SSL to connect to, Sandbox or Production
	 * @access private
	 */
	public function checkFeedback($development = null) {
		if (!$development) {
			$development = 'production';
		}
		$ctx = stream_context_create();
		stream_context_set_option($ctx, 'ssl', 'local_cert', $this->apnsData[$development]['certificate']);
		stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
		if ($this->apnsData[$development]['passphrase']) {
			stream_context_set_option($ctx, 'ssl', 'passphrase', $this->apnsData[$development]['passphrase']);
		}
		$fp = @stream_socket_client($this->apnsData[$development]['feedback'], $error, $errorString, 100, (STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT), $ctx);

		if (!$fp) {
			writeLog("Failed to connect to feedback service: {$error} {$errorString}.\r\n", 'apns_feedback');
			return null;
		}
		$tokens = array();
		while ($devcon = fread($fp, 38)) {
			$arr = unpack("H*", $devcon);
			$rawhex = trim(implode("", $arr));
			$token = substr($rawhex, 12, 64);
			if (!empty($token)) {
				$tokens[] = $token;
			}
		}
		fclose($fp);
		return $tokens;
	}

}

?>

